package gen

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/zeromicro/go-zero/core/collection"
	"github.com/zeromicro/go-zero/tools/goctl/model/sql/parser"
	"github.com/zeromicro/go-zero/tools/goctl/model/sql/template"
	"github.com/zeromicro/go-zero/tools/goctl/util"
	"github.com/zeromicro/go-zero/tools/goctl/util/format"
	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
	"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
)

func (g *defaultGenerator) createServerFile(modelList map[string]*codeServerTuple, register map[string]string, cmdReg string) error {
	dirAbs, err := filepath.Abs(g.dir)
	if err != nil {
		return err
	}
	dirAbs = filepath.Join(dirAbs, "server")
	g.pkg = util.SafeString(filepath.Base(dirAbs))
	err = pathx.MkdirIfNotExist(dirAbs)
	if err != nil {
		return err
	}

	command := filepath.Join("cmd/server", "main.go")
	err = os.WriteFile(command, []byte(cmdReg), os.ModePerm)
	if err != nil {
		return err
	}

	for tableName, code := range register {
		tn := stringx.From(tableName)
		modelFilename, err := format.FileNamingFormat(g.cfg.NamingFormat,
			fmt.Sprintf("%s_model", tn.Source()))
		if err != nil {
			return err
		}

		dir := filepath.Join(dirAbs, modelFilename)
		err = pathx.MkdirIfNotExist(dir)
		if err != nil {
			return err
		}

		name := "register_gen.go"
		filename := filepath.Join(dir, name)

		err = os.WriteFile(filename, []byte(code), os.ModePerm)
		if err != nil {
			return err
		}
	}

	for tableName, codes := range modelList {
		tn := stringx.From(tableName)
		modelFilename, err := format.FileNamingFormat(g.cfg.NamingFormat,
			fmt.Sprintf("%s_model", tn.Source()))
		if err != nil {
			return err
		}

		dir := filepath.Join(dirAbs, modelFilename)
		err = pathx.MkdirIfNotExist(dir)
		if err != nil {
			return err
		}

		name := "insert_gen.go"
		filename := filepath.Join(dir, name)
		err = os.WriteFile(filename, []byte(codes.modelCodeInsert), os.ModePerm)
		if err != nil {
			return err
		}

		for key, code := range codes.modelCodeFind {
			name = fmt.Sprintf("find_gen_%d.go", key)
			filename = filepath.Join(dir, name)
			if code != "" {
				err = os.WriteFile(filename, []byte(code), os.ModePerm)
				if err != nil {
					return err
				}
			}
		}

		name = "delete_gen.go"
		filename = filepath.Join(dir, name)
		err = os.WriteFile(filename, []byte(codes.modelCodeDelete), os.ModePerm)
		if err != nil {
			return err
		}

		name = "update_gen.go"
		filename = filepath.Join(dir, name)
		err = os.WriteFile(filename, []byte(codes.modelCodeUpdate), os.ModePerm)
		if err != nil {
			return err
		}
	}

	return nil
}

type codeServerTuple struct {
	modelCodeUpdate string
	modelCodeInsert string
	modelCodeDelete string
	modelCodeFind   []string
}

func (g *defaultGenerator) genModelServerCmdCode(pkgs []string) (string, error) {

	regs := make([]string, 0)
	imports := make([]string, 0)
	for _, pkg := range pkgs {
		reg := fmt.Sprintf("%s.RegisterMethod(conn)", pkg)
		regs = append(regs, reg)
		impot := fmt.Sprintf("\"orchestrator/internal/infra/database/flex/po/server/%s\"", pkg)
		imports = append(imports, impot)
	}

	text, err := pathx.LoadTemplate(category, serverFindOneTpl, template.ServerCommand)
	if err != nil {
		return "", err
	}

	output, err := util.With("register").
		Parse(text).
		GoFmt(true).
		Execute(map[string]any{
			"imports":   strings.Join(imports, "\n"),
			"registers": strings.Join(regs, "\n"),
		})
	if err != nil {
		return "", err
	}
	return output.String(), nil

}

func (g *defaultGenerator) genModelServer(in parser.Table, pkg string) (*code, string, error) {
	if len(in.PrimaryKey.Name.Source()) == 0 {
		return nil, "", fmt.Errorf("table %s: missing primary key", in.Name.Source())
	}

	primaryKey, uniqueKey := genCacheKeys(in)

	var table Table
	table.Table = in
	table.PrimaryCacheKey = primaryKey
	table.UniqueCacheKey = uniqueKey
	table.ContainsUniqueCacheKey = len(uniqueKey) > 0
	table.ignoreColumns = g.ignoreColumns

	insertCode, insertRegisterCode, err := genServerInsert(table, pkg)
	if err != nil {
		return nil, "", err
	}

	findCode := make([]string, 0)
	findOneCode, findRegisterCode, err := genServerFindOne(table, pkg)
	if err != nil {
		return nil, "", err
	}

	ret, err := genServerFindOneByField(table, pkg)
	if err != nil {
		return nil, "", err
	}

	findCode = append(findCode, findOneCode, ret.findOneMethod)
	updateCode, updateRegisterCode, err := genServerUpdate(table, pkg)
	if err != nil {
		return nil, "", err
	}

	deleteCode, deleteRegisterCode, err := genServerDelete(table, pkg)
	if err != nil {
		return nil, "", err
	}

	regCodes := strings.Join([]string{insertRegisterCode, findRegisterCode, ret.findOneInterfaceMethod, updateRegisterCode, deleteRegisterCode}, "\n")
	registerCode, err := genServerRegisterCode(table, regCodes, pkg)
	if err != nil {
		return nil, "", err
	}

	code := &code{
		insertCode: insertCode,
		findCode:   findCode,
		updateCode: updateCode,
		deleteCode: deleteCode,
	}

	return code, registerCode, nil
}

func (g *defaultGenerator) executeModelServer(table Table, code *code) (*bytes.Buffer, error) {
	text, err := pathx.LoadTemplate(category, modelGenTemplateFile, template.ModelGen)
	if err != nil {
		return nil, err
	}
	t := util.With("model").
		Parse(text).
		GoFmt(true)
	output, err := t.Execute(map[string]any{
		"pkg":        g.pkg,
		"imports":    code.importsCode,
		"vars":       code.varsCode,
		"types":      code.typesCode,
		"new":        code.newCode,
		"insert":     code.insertCode,
		"find":       strings.Join(code.findCode, "\n"),
		"update":     code.updateCode,
		"delete":     code.deleteCode,
		"tableName":  code.tableName,
		"data":       table,
		"customized": code.customizedCode,
	})
	if err != nil {
		return nil, err
	}
	return output, nil
}
func genServerRegisterCode(table Table, code string, pkg string) (string, error) {

	camel := table.Name.ToCamel()
	text, err := pathx.LoadTemplate(category, serverFindOneTpl, template.ServerRegister)
	if err != nil {
		return "", err
	}

	fields := table.Fields
	fieldsString, err := genFields(table, fields)
	if err != nil {
		return "", err
	}

	fieldsMock, err := genFieldsMock(table, fields)
	if err != nil {
		return "", err
	}

	tablename := table.Name.Source()
	output, err := util.With("register").
		Parse(text).
		GoFmt(true).
		Execute(map[string]any{
			"fieldsMock":                fieldsMock,
			"pkg":                       pkg,
			"table":                     tablename,
			"fields":                    fieldsString,
			"codes":                     code,
			"upperStartCamelObject":     camel,
			"lowerStartCamelObject":     stringx.From(camel).Untitle(),
			"originalPrimaryKey":        wrapWithRawString(table.PrimaryKey.Name.Source(), false),
			"upperStartCamelPrimaryKey": util.EscapeGolangKeyword(stringx.From(table.PrimaryKey.Name.ToCamel()).Title()),
			"lowerStartCamelPrimaryKey": util.EscapeGolangKeyword(stringx.From(table.PrimaryKey.Name.ToCamel()).Untitle()),
			"dataType":                  table.PrimaryKey.DataType,
			"data":                      table,
		})
	if err != nil {
		return "", err
	}
	return output.String(), nil
}

func genServerInsert(table Table, pkg string) (string, string, error) {
	keySet := collection.NewSet()
	keyVariableSet := collection.NewSet()
	keySet.AddStr(table.PrimaryCacheKey.DataKeyExpression)
	keyVariableSet.AddStr(table.PrimaryCacheKey.KeyLeft)
	for _, key := range table.UniqueCacheKey {
		keySet.AddStr(key.DataKeyExpression)
		keyVariableSet.AddStr(key.KeyLeft)
	}
	keys := keySet.KeysStr()
	sort.Strings(keys)
	keyVars := keyVariableSet.KeysStr()
	sort.Strings(keyVars)

	expressions := make([]string, 0)
	expressionValues := make([]string, 0)
	var count int
	for _, field := range table.Fields {
		camel := util.SafeString(field.Name.ToCamel())
		if table.isIgnoreColumns(field.Name.Source()) {
			continue
		}

		if field.Name.Source() == table.PrimaryKey.Name.Source() {
			if table.PrimaryKey.AutoIncrement {
				continue
			}
		}

		count += 1

		expressions = append(expressions, "?")
		expressionValues = append(expressionValues, "data."+camel)
	}

	camel := table.Name.ToCamel()
	text, err := pathx.LoadTemplate(category, serverInsertTpl, template.ServerInsert)
	if err != nil {
		return "", "", err
	}

	tablename := table.Name.Source()
	output, err := util.With("insert").
		Parse(text).
		GoFmt(true).
		Execute(map[string]any{
			"pkg":                   pkg,
			"table":                 tablename,
			"upperStartCamelObject": camel,
			"lowerStartCamelObject": stringx.From(camel).Untitle(),
			"expression":            strings.Join(expressions, ", "),
			"expressionValues":      strings.Join(expressionValues, ", "),
			"keys":                  strings.Join(keys, "\n"),
			"keyValues":             strings.Join(keyVars, ", "),
			"data":                  table,
		})
	if err != nil {
		return "", "", err
	}

	// // interface method
	// text, err = pathx.LoadTemplate(category, insertTemplateMethodFile, template.InsertMethod)
	// if err != nil {
	// 	return "", "", err
	// }

	text = "jrpc.Register(&{{.upperStartCamelObject}}InsertFunc{Db: conn, Repo: m})"
	insertMethodOutput, err := util.With("insertMethod").Parse(text).Execute(map[string]any{
		"upperStartCamelObject": camel,
		"data":                  table,
	})
	if err != nil {
		return "", "", err
	}

	return output.String(), insertMethodOutput.String(), nil
}

func genServerFindOne(table Table, pkg string) (string, string, error) {
	camel := table.Name.ToCamel()
	text, err := pathx.LoadTemplate(category, serverFindOneTpl, template.ServerFind)
	if err != nil {
		return "", "", err
	}

	fields := table.Fields
	fieldsString, err := genFields(table, fields)
	if err != nil {
		return "", "", err
	}

	tablename := table.Name.Source()
	output, err := util.With("findOne").
		Parse(text).
		GoFmt(true).
		Execute(map[string]any{
			"pkg":                       pkg,
			"table":                     tablename,
			"fields":                    fieldsString,
			"upperStartCamelObject":     camel,
			"lowerStartCamelObject":     stringx.From(camel).Untitle(),
			"originalPrimaryKey":        wrapWithRawString(table.PrimaryKey.Name.Source(), false),
			"upperStartCamelPrimaryKey": util.EscapeGolangKeyword(stringx.From(table.PrimaryKey.Name.ToCamel()).Title()),
			"lowerStartCamelPrimaryKey": util.EscapeGolangKeyword(stringx.From(table.PrimaryKey.Name.ToCamel()).Untitle()),
			"dataType":                  table.PrimaryKey.DataType,
			"data":                      table,
		})
	if err != nil {
		return "", "", err
	}

	// text, err = pathx.LoadTemplate(category, findOneMethodTemplateFile, template.FindOneMethod)
	// if err != nil {
	// 	return "", "", err
	// }

	text = strings.Join([]string{
		"jrpc.Register(&{{.upperStartCamelObject}}FindOneFunc{Db: conn, Repo: m})",
		"jrpc.Register(&{{.upperStartCamelObject}}ListFunc{Db: conn, Repo: m})",
		// "jrpc.Register(&{{.upperStartCamelObject}}CountFunc{})",
	}, "\n")
	findOneMethod, err := util.With("findOneMethod").
		Parse(text).
		Execute(map[string]any{
			"upperStartCamelObject":     camel,
			"lowerStartCamelPrimaryKey": util.EscapeGolangKeyword(stringx.From(table.PrimaryKey.Name.ToCamel()).Untitle()),
			"dataType":                  table.PrimaryKey.DataType,
			"data":                      table,
		})
	if err != nil {
		return "", "", err
	}

	return output.String(), findOneMethod.String(), nil
}

func genServerFindOneByField(table Table, pkg string) (*findOneCode, error) {
	var list []string

	text, err := pathx.LoadTemplate(category, serverFindOneByFieldTpl, template.ServerFindByFieldImport)
	if err != nil {
		return nil, err
	}

	t := util.With("findOneByFieldHeader").Parse(text)
	output, err := t.
		Execute(map[string]any{
			"pkg": pkg,
		})
	if err != nil {
		return nil, err
	}

	header := output.String()

	text, err = pathx.LoadTemplate(category, serverFindOneByFieldTpl, template.ServerFindByField)
	if err != nil {
		return nil, err
	}

	tablename := table.Name.Source()

	t = util.With("findOneByField").Parse(text)

	camelTableName := table.Name.ToCamel()

	var list1 []string
	for _, key := range table.UniqueCacheKey {
		in, paramJoinString, originalFieldString := convertJoin(key, false)

		ins := strings.Split(in, ",")
		for index, i := range ins {
			ins[index] = strings.ToUpper(string(i[0])) + i[1:]
		}
		in = strings.Join(ins, "\n")

		output, err := t.Execute(map[string]any{
			"table":                     tablename,
			"upperStartCamelObject":     camelTableName,
			"upperField":                key.FieldNameJoin.Camel().With("").Source(),
			"in":                        in,
			"lowerStartCamelObject":     stringx.From(camelTableName).Untitle(),
			"lowerStartCamelField":      paramJoinString,
			"upperStartCamelPrimaryKey": table.PrimaryKey.Name.ToCamel(),
			"originalField":             originalFieldString,
			"data":                      table,
		})
		if err != nil {
			return nil, err
		}

		list1 = append(list1, output.String())
	}

	gentmpl := "{{.header}}\n{{.list}}"
	text, err = pathx.LoadTemplate(category, findOneByFieldMethodTemplateFile, gentmpl)
	if err != nil {
		return nil, err
	}

	if len(list1) != 0 {

		t = util.With("findOneByFieldGen").Parse(text)
		gen, err := t.GoFmt(true).
			Execute(map[string]any{
				"header": header,
				"list":   strings.Join(list1, "\n"),
			})
		if err != nil {
			return nil, err
		}
		list = append(list, gen.String())
	}

	text = strings.Join([]string{
		"jrpc.Register(&{{.upperStartCamelObject}}FindOneBy{{.upperField}}Func{Db: conn, Repo: m})",
	}, "\n")

	t = util.With("findOneByFieldMethod").Parse(text)
	var listMethod []string
	for _, key := range table.UniqueCacheKey {
		var inJoin, paramJoin Join
		for _, f := range key.Fields {
			param := util.EscapeGolangKeyword(stringx.From(f.Name.ToCamel()).Untitle())
			inJoin = append(inJoin, fmt.Sprintf("%s %s", param, f.DataType))
			paramJoin = append(paramJoin, param)
		}

		var in string
		if len(inJoin) > 0 {
			in = inJoin.With(", ").Source()
		}
		output, err := t.Execute(map[string]any{
			"upperStartCamelObject": camelTableName,
			"upperField":            key.FieldNameJoin.Camel().With("").Source(),
			"in":                    in,
			"data":                  table,
		})
		if err != nil {
			return nil, err
		}

		listMethod = append(listMethod, output.String())
	}

	return &findOneCode{
		findOneMethod:          strings.Join(list, pathx.NL),
		findOneInterfaceMethod: strings.Join(listMethod, pathx.NL),
	}, nil
}

func genServerUpdate(table Table, pkg1 string) (
	string, string, error,
) {
	expressionValues := make([]string, 0)
	pkg := "data."
	if table.ContainsUniqueCacheKey {
		pkg = "newData."
	}
	for _, field := range table.Fields {
		camel := util.SafeString(field.Name.ToCamel())
		if table.isIgnoreColumns(field.Name.Source()) {
			continue
		}

		if field.Name.Source() == table.PrimaryKey.Name.Source() {
			continue
		}

		expressionValues = append(expressionValues, pkg+camel)
	}

	keySet := collection.NewSet()
	keyVariableSet := collection.NewSet()
	keySet.AddStr(table.PrimaryCacheKey.DataKeyExpression)
	keyVariableSet.AddStr(table.PrimaryCacheKey.KeyLeft)
	for _, key := range table.UniqueCacheKey {
		keySet.AddStr(key.DataKeyExpression)
		keyVariableSet.AddStr(key.KeyLeft)
	}
	keys := keySet.KeysStr()
	sort.Strings(keys)
	keyVars := keyVariableSet.KeysStr()
	sort.Strings(keyVars)

	expressionValues = append(
		expressionValues, pkg+table.PrimaryKey.Name.ToCamel(),
	)
	camelTableName := table.Name.ToCamel()
	text, err := pathx.LoadTemplate(category, serverUpdateTpl, template.ServerUpdate)
	if err != nil {
		return "", "", err
	}

	tablename := table.Name.Source()

	output, err := util.With("update").Parse(text).
		GoFmt(true).
		Execute(
			map[string]any{
				"pkg":                   pkg1,
				"table":                 tablename,
				"containsIndexCache":    table.ContainsUniqueCacheKey,
				"upperStartCamelObject": camelTableName,
				"keys":                  strings.Join(keys, "\n"),
				"keyValues":             strings.Join(keyVars, ", "),
				"primaryCacheKey":       table.PrimaryCacheKey.DataKeyExpression,
				"primaryKeyVariable":    table.PrimaryCacheKey.KeyLeft,
				"lowerStartCamelObject": stringx.From(camelTableName).Untitle(),
				"upperStartCamelPrimaryKey": util.EscapeGolangKeyword(
					stringx.From(table.PrimaryKey.Name.ToCamel()).Title(),
				),
				"originalPrimaryKey": wrapWithRawString(
					table.PrimaryKey.Name.Source(), false,
				),
				"expressionValues": strings.Join(
					expressionValues, ", ",
				),
				"data": table,
			},
		)
	if err != nil {
		return "", "", nil
	}

	// update interface method
	// text, err = pathx.LoadTemplate(category, updateMethodTemplateFile, template.UpdateMethod)
	// if err != nil {
	// 	return "", "", err
	// }

	text = strings.Join([]string{
		"jrpc.Register(&{{.upperStartCamelObject}}UpdateFunc{Db: conn, Repo: m})",
		"jrpc.Register(&{{.upperStartCamelObject}}UpdateByFieldsFunc{Db: conn, Repo: m})",
	}, "\n")

	updateMethodOutput, err := util.With("updateMethod").Parse(text).Execute(
		map[string]any{
			"upperStartCamelObject": camelTableName,
			"data":                  table,
		},
	)
	if err != nil {
		return "", "", nil
	}

	return output.String(), updateMethodOutput.String(), nil
}

func genServerDelete(table Table, pkg string) (string, string, error) {
	keySet := collection.NewSet()
	keyVariableSet := collection.NewSet()
	keySet.AddStr(table.PrimaryCacheKey.KeyExpression)
	keyVariableSet.AddStr(table.PrimaryCacheKey.KeyLeft)
	for _, key := range table.UniqueCacheKey {
		keySet.AddStr(key.DataKeyExpression)
		keyVariableSet.AddStr(key.KeyLeft)
	}
	keys := keySet.KeysStr()
	sort.Strings(keys)
	keyVars := keyVariableSet.KeysStr()
	sort.Strings(keyVars)

	camel := table.Name.ToCamel()
	text, err := pathx.LoadTemplate(category, serverDeleteTpl, template.ServerDelete)
	if err != nil {
		return "", "", err
	}

	tablename := table.Name.Source()

	output, err := util.With("delete").
		Parse(text).
		GoFmt(true).
		Execute(map[string]any{
			"pkg":                       pkg,
			"table":                     tablename,
			"upperStartCamelObject":     camel,
			"containsIndexCache":        table.ContainsUniqueCacheKey,
			"lowerStartCamelPrimaryKey": util.EscapeGolangKeyword(stringx.From(table.PrimaryKey.Name.ToCamel()).Untitle()),
			"upperStartCamelPrimaryKey": util.EscapeGolangKeyword(stringx.From(table.PrimaryKey.Name.ToCamel()).Title()),
			"dataType":                  table.PrimaryKey.DataType,
			"keys":                      strings.Join(keys, "\n"),
			"originalPrimaryKey":        wrapWithRawString(table.PrimaryKey.Name.Source(), false),
			"keyValues":                 strings.Join(keyVars, ", "),
			"data":                      table,
		})
	if err != nil {
		return "", "", err
	}

	// interface method
	// text, err = pathx.LoadTemplate(category, deleteMethodTemplateFile, template.DeleteMethod)
	// if err != nil {
	// 	return "", "", err
	// }

	text = strings.Join([]string{
		"jrpc.Register(&{{.upperStartCamelObject}}DeleteFunc{Db: conn, Repo: m})",
		"jrpc.Register(&{{.upperStartCamelObject}}DeleteByFilterFunc{Db: conn, Repo: m})",
	}, "\n")
	deleteMethodOut, err := util.With("deleteMethod").
		Parse(text).
		Execute(map[string]any{
			"upperStartCamelObject":     camel,
			"lowerStartCamelPrimaryKey": util.EscapeGolangKeyword(stringx.From(table.PrimaryKey.Name.ToCamel()).Untitle()),
			"upperStartCamelPrimaryKey": util.EscapeGolangKeyword(stringx.From(table.PrimaryKey.Name.ToCamel()).Title()),
			"dataType":                  table.PrimaryKey.DataType,
			"data":                      table,
		})
	if err != nil {
		return "", "", err
	}

	return output.String(), deleteMethodOut.String(), nil
}
